package com.nts.bupna.generator;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.JFrame;
import com.mxgraph.layout.mxCompactTreeLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.view.mxGraph;
import com.nts.bupna.model.Activity;
import com.nts.bupna.model.ActivityType;
import com.nts.bupna.model.If;
import com.nts.bupna.model.If.Else;
import com.nts.bupna.model.If.ElseIf;
import com.nts.bupna.model.Process;
import com.nts.bupna.model.Receive;
import com.nts.bupna.model.Reply;
import com.nts.bupna.model.Sequence;

public class ProcessToGraph {


    public static JFrame generateGraph(Process process) {
        mxGraph graph = new mxGraph();
        Object parent = graph.getDefaultParent();
        //graph.orderCells(true);
        graph.getModel().beginUpdate();
        try {
            Receive receive = process.getReceive();
            Object root = graph.insertVertex(parent, null, receive.toString(), 20, 20, 80, 30);
            ActivityGraph activityGraph = generateActivityGraph(graph, process.getActivity(), root, false);
            graph.insertEdge(parent, null, "", root, activityGraph.getRoot());

            Reply reply = process.getReply();
            if(reply != null) {
                Object end = graph.insertVertex(parent, null, reply.toString(), 240, 150, 80, 30);
                for(Object endPoint : activityGraph.getEndPoints()) {
                    graph.insertEdge(parent, null, "", endPoint, end);
                }
            }
        } finally {
            graph.getModel().endUpdate();
        }

        mxCompactTreeLayout layout = new mxCompactTreeLayout(graph, false);
        layout.execute(graph.getDefaultParent());
        mxGraphComponent graphComponent = new mxGraphComponent(graph);

        JFrame f = new JFrame();
        f.getContentPane().add(graphComponent);
        f.pack();

        return f;
    }

    private static ActivityGraph generateActivityGraph(mxGraph graph, Activity activity, Object p, boolean applyParent) {
        Object parent = p;
        Object defaultParent = graph.getDefaultParent();
        if(parent == null) {
            parent = defaultParent;
        }
        ActivityType activityType = activity.getActivityType();
        Object activityRoot;
        if(applyParent) {
            activityRoot = graph.insertVertex(parent, null, activityType, 20, 20, 80, 30);
        } else {
            activityRoot = graph.insertVertex(defaultParent, null, activityType, 20, 20, 80, 30);
        }
        List<Object> endPoints = Arrays.asList(activityRoot);
        switch (activityType) {
        case SEQUENCE:
            Sequence seq = (Sequence)activity;
            List<Object> currentNodes = Arrays.asList(parent);
            boolean isFirst = true;
            for (Activity a : seq) {
                ActivityGraph aGraph = generateActivityGraph(graph, a, activityRoot, true);
                if(isFirst) {
                    for(Object endPoint : currentNodes) {
                        graph.insertEdge(defaultParent, null, "", endPoint, aGraph.getRoot());
                    }
                    isFirst = false;
                }
                currentNodes = aGraph.getEndPoints();
            }
            mxCompactTreeLayout layout = new mxCompactTreeLayout(graph, false);
            layout.execute(activityRoot);
            endPoints = currentNodes;
            break;
        case IF:
            boolean collapsableIf = true;
            If theIf = (If)activity;
            ActivityGraph ifGraph = generateActivityGraph(graph, theIf.getActivity(), parent, collapsableIf);
            graph.insertEdge(defaultParent, null, "", activityRoot, ifGraph.getRoot());
            endPoints = ifGraph.getEndPoints();
            for (ElseIf elseIf : theIf) {
                ActivityGraph elseIfGraph = generateActivityGraph(graph, elseIf.getActivity(), parent, collapsableIf);
                graph.insertEdge(defaultParent, null, "", activityRoot, elseIfGraph.getRoot());
                if(endPoints != null) {
                    endPoints.addAll(elseIfGraph.getEndPoints());
                } else {
                    endPoints = elseIfGraph.getEndPoints();
                }
            }
            Else theElse = theIf.getElse();
            if(theElse != null) {
                ActivityGraph elseGraph = generateActivityGraph(graph, theElse.getActivity(), parent, collapsableIf);
                graph.insertEdge(defaultParent, null, "", activityRoot, elseGraph.getRoot());
                if(endPoints != null) {
                    endPoints.addAll(elseGraph.getEndPoints());
                } else {
                    endPoints = elseGraph.getEndPoints();
                }
            } else {
                if(endPoints != null) {
                    endPoints.add(activityRoot);
                } else {
                    endPoints = Arrays.asList(activityRoot);
                }
            }
            break;
        case THROW:
            endPoints = null;
            break;
        default:
            break;
        }
        return new ActivityGraph(activityRoot, endPoints);
    }

    private static class ActivityGraph {
        private final Object root;
        private final List<Object> endPoints;

        public ActivityGraph(Object root, List<Object> endPoints) {
            this.root = root;
            this.endPoints = endPoints != null ? new ArrayList<Object>(endPoints) : null;
        }

        public Object getRoot() {
            return root;
        }

        public List<Object> getEndPoints() {
            return endPoints;
        }
    }


    public JFrame draw(Process process) {
        JFrame frame = new JFrame();
        drawGraph(process);
        frame.getContentPane().add(new mxGraphComponent(graph));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        return frame;
    }

    private mxGraph drawGraph(Process process) {
        graph = new mxGraph();
        graph.getModel().beginUpdate();
        try {
            Object receiveVertex = draw(process.getReceive());
            Object activityVertex = draw(process.getActivity(), graph.getDefaultParent(), false);
            connect(receiveVertex, activityVertex);
            Object replyVertex = draw(process.getReply());
            connect(activityVertex, replyVertex);
        } finally {
            graph.getModel().endUpdate();
        }
        return graph;
    }

    private void connect(Object source, Object target, String label) {
        graph.insertEdge(graph.getDefaultParent(), null, label, source, target);
    }

    private void connect(Object source, Object target) {
        connect(source, target, null);
    }

    Object last;
    mxGraph graph;

    private Object draw(Reply reply) {
        Object vertex = graph.insertVertex(graph.getDefaultParent(), null, "Reply", 20, below(), 120, 30);
        return vertex;
    }

    private mxRectangle getBounds() {
        mxCell cell = (mxCell) last;
        return cell.getGeometry();
    }

    private double below() {
        mxRectangle bounds = getBounds();
        return bounds.getY() + bounds.getHeight() + 10;
    }

    private double inside(Object parent) {
        return ((mxCell)parent).getGeometry().getY() + 10;
    }

    private Object draw(Activity activity, Object parent, boolean insider) {
        Drawer drawer = drawers.get(activity.getActivityType());
        if (drawer != null) {
            return drawer.draw(activity, parent, insider);
        }

        Object vertex = graph.insertVertex(parent, null, activity.getActivityType().toString(), 20, insider ? inside(parent) : below(), 120, 30);
        last = vertex;
        return vertex;

    }

    private Object draw(Receive receive) {
        Object vertex = graph.insertVertex(graph.getDefaultParent(), null, "Receive", 20, 20, 120, 30);
        last = vertex;
        return vertex;
    }

    interface Drawer {
        Object draw(Activity a, Object parent, boolean insider);
    }

    Map<ActivityType, Drawer> drawers = new HashMap<ActivityType, ProcessToGraph.Drawer>();
    {
        drawers.put(ActivityType.SEQUENCE, new Drawer() {
            @Override
            public Object draw(Activity a, Object parent, boolean insider) {
                Sequence seq = (Sequence) a;
                Object seqVertex = graph.insertVertex(parent, null, "Sequence", 20, insider ? inside(parent) : below(), 120, 30);
                Object lastVertex = null;
                last = seqVertex;
                boolean first = true;
                for (Activity activity : seq) {
                    Object current = ProcessToGraph.this.draw(activity, seqVertex, first);
                    first = false;
                    if (lastVertex != null) {
                        connect(lastVertex, current);
                    }
                    lastVertex = current;
                }
                last = seqVertex;
                return seqVertex;
            }
        });

        drawers.put(ActivityType.IF, new Drawer() {

            @Override
            public Object draw(Activity a, Object parent, boolean insider) {
                If theIf = (If) a;
                Object ifContainer = graph.insertVertex(parent, null, "Choose", 20, insider ? inside(parent) : below(), 120, 30);
                last = ifContainer;
                Object ifVertexStart = graph.insertVertex(ifContainer, null, "If", 20, inside(ifContainer), 50, 50);
                last = ifVertexStart;
                Object ifVertexEnd = graph.insertVertex(ifContainer, null, "EndIf", 20, below(), 50, 50);
                connect(ifVertexStart, ifVertexEnd);
                last = ifContainer;
                return ifContainer;
            }
        });
    }
}
